// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "main.h"
#include "syscall.h"
#include "ksyscall.h"

void ModifyPC() {
	/* Modify return point */
	/* set previous programm counter (debugging only)*/
	kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(
			PCReg));

	/* set programm counter to next instruction (all Instructions are 4 byte wide)*/
	kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg)
			+ 4);

	/* set next programm counter for brach execution */
	kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(
			PCReg) + 4);
}

//----------------------------------------------------------------------
// ExceptionHandler
// 	Entry point into the Nachos kernel.  Called when a user program
//	is executing, and either does a syscall, or generates an addressing
//	or arithmetic exception.
//
// 	For system calls, the following is the calling convention:
//
// 	system call code -- r2
//		arg1 -- r4
//		arg2 -- r5
//		arg3 -- r6
//		arg4 -- r7
//
//	The result of the system call, if any, must be put back into r2. 
//
// If you are handling a system call, don't forget to increment the pc
// before returning. (Or else you'll loop making the same system call forever!)
//
//	"which" is the kind of exception.  The list of possible exceptions 
//	is in machine.h.
//----------------------------------------------------------------------

void ExceptionHandler(ExceptionType which) {
	int type = kernel->machine->ReadRegister(2);
	int result;

	DEBUG(dbgSys, "Received Exception " << which << " type: " << type << "\n");

	switch (which) {
	case SyscallException:
		switch (type) {
		case SC_Halt:
			DEBUG(dbgSys, "Shutdown, initiated by user program.\n")
			;

			SysHalt();

			ASSERTNOTREACHED()
			;
			break;

		case SC_Exec: {
			/* Modify return point */
			ModifyPC();
			result = SysExec((char *) kernel->machine->ReadRegister(4));
			kernel->machine->WriteRegister(2, (int) result);

			return;
			ASSERTNOTREACHED();
			break;
		}

		case SC_Exit: {
			result = SysExit(kernel->machine->ReadRegister(4));
			kernel->machine->WriteRegister(2, (int) result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}
		case SC_Print: {
			result = SysPrint((char *) kernel->machine->ReadRegister(4));
			kernel->machine->WriteRegister(2, (int) result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}
		case SC_PrintNum: {
			result = SysPrintNum((int) kernel->machine->ReadRegister(4));
			kernel->machine->WriteRegister(2, (int) result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

		case SC_Add: {
			DEBUG(dbgSys, "Add " << kernel->machine->ReadRegister(4) << " + " << kernel->machine->ReadRegister(5) << "\n");

			/* Process SysAdd Systemcall*/
			result = SysAdd(
			/* int op1 */(int) kernel->machine->ReadRegister(4),
			/* int op2 */(int) kernel->machine->ReadRegister(5));

			DEBUG(dbgSys, "Add returning with " << result << "\n");
			/* Prepare Result */
			kernel->machine->WriteRegister(2, (int) result);

			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

		case SC_Write: {
			int Addr = kernel->machine->ReadRegister(4); // write what?
			int size = kernel->machine->ReadRegister(5); // write how many bytes
			OpenFileId fd = kernel->machine->ReadRegister(6); // write to where?

			result = SysWrite(fd, Addr, size);
			kernel->machine->WriteRegister(2, (int) result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

		case SC_Read: {
			int destAddr = kernel->machine->ReadRegister(4); // where to write to
			int size = kernel->machine->ReadRegister(5); // Read how many bytes
			OpenFileId fd = kernel->machine->ReadRegister(6); // read from where?

			result = SysRead(fd, destAddr, size);
			kernel->machine->WriteRegister(2, (int) result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

        case SC_ChDir: {
			int pathAddr = kernel->machine->ReadRegister(4); 
			
			result = SysChDir(pathAddr);
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

        case SC_GetCwd: {
			int pathAddr = kernel->machine->ReadRegister(4); 
			int size = kernel->machine->ReadRegister(5); 

			result = SysGetCwd( pathAddr, size);
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

        case SC_GetDentry: {
			int pathAddr = kernel->machine->ReadRegister(4); 
			int dirEntAddr = kernel->machine->ReadRegister(5); 
            int countAddr = kernel->machine->ReadRegister(6);

			result = SysGetDent( pathAddr, dirEntAddr, countAddr );
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

        case SC_MkDir: {
			int pathAddr = kernel->machine->ReadRegister(4); 

			result = SysMkDir( pathAddr );
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

        case SC_Create: {
			int pathAddr = kernel->machine->ReadRegister(4); 

			result = SysCreate( pathAddr );
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

        case SC_RmDir: {
			int pathAddr = kernel->machine->ReadRegister(4); 
            int resv = kernel->machine->ReadRegister(5);

			result = SysRmDir( pathAddr, resv );
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();

			return;
			ASSERTNOTREACHED();
			break;
		}

	case SC_Rename: {
			 int pathAddr = kernel->machine->ReadRegister(4);
			 int nameAddr = kernel->machine->ReadRegister(5);

			 result = SysRename( pathAddr, nameAddr);
			 kernel->machine->WriteRegister(2, (int)result);
			 ModifyPC();
	
			 return;
			 ASSERTNOTREACHED();
			 break;
		}

	case SC_Remove: {
			 int pathAddr = kernel->machine->ReadRegister(4);
			 
			 result = SysRemove( pathAddr);
			 kernel->machine->WriteRegister(2, (int)result);
			 ModifyPC();
	
			 return;
			 ASSERTNOTREACHED();
			 break;
		}
    
	case SC_Copy: {
			int spathAddr = kernel->machine->ReadRegister(4);
			int dpathAddr = kernel->machine->ReadRegister(5);

			result = SysCopy( spathAddr, dpathAddr );
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();
	
			return;
			ASSERTNOTREACHED();
			break;
		}

	case SC_Append: {
			int spathAddr = kernel->machine->ReadRegister(4);
			int dpathAddr = kernel->machine->ReadRegister(5);

			result = SysAppend( spathAddr, dpathAddr );
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();
	
			return;
			ASSERTNOTREACHED();
			break;
		}

	case SC_List: {
			int pathAddr = kernel->machine->ReadRegister(4);
			int isResv = kernel->machine->ReadRegister(5);

			result = SysList( pathAddr, (bool)isResv );
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();
	
			return;
			ASSERTNOTREACHED();
			break;
		}
    
    case SC_Cat: {
			int pathAddr = kernel->machine->ReadRegister(4);

			result = SysCat( pathAddr );
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();
	
			return;
			ASSERTNOTREACHED();
			break;
		}

    case SC_GetFile: {
			int fromFileAddr = kernel->machine->ReadRegister(4);
			int toFileAddr = kernel->machine->ReadRegister(5);

			result = SysGetFile( fromFileAddr, toFileAddr );
			kernel->machine->WriteRegister(2, (int)result);
			ModifyPC();
	
			return;
			ASSERTNOTREACHED();
			break;
		}

		default: {
			cerr << "Unexpected system call " << type << "\n";
			break;
		}
		}
		break;
	default:
		cerr << "Unexpected user mode exception " << (int) which << "\n";
		break;
	}
	ASSERTNOTREACHED();
}
